#include <inarea_config.h>
#include <tsd/inarea.h>
#include <tsd/tsd.h>

// defines an area of touch
typedef struct
{
    inarea_callback_t prs; // called when pressed
    inarea_callback_t mvo; // called when "pressing" moves out
    inarea_callback_t rls; // called when released
    inarea_rect_t* lst; // rectangles that compose this area
    int nor; // number of rectangles that compose this area
} _inarea;

// all of 'em
static _inarea _areas [INAREA_CONFIG_MAXIMUM_AREAS];
// the current's id
static inarea_id_t _curr_area = INAREA_CONFIG_MAXIMUM_AREAS;
static int _has_moved_out = 0;

inarea_id_t TSD_InareaAdd (
        inarea_callback_t pressed,
        inarea_callback_t moved_out,
        inarea_callback_t released,
        inarea_rect_t* rect_list,
        int num_of_rect)
{
    int i;
    _inarea * area;

    // look for an available slot
    for (i = 0; i < INAREA_CONFIG_MAXIMUM_AREAS; ++i)
    {
        area = _areas + i;
        if (area->nor == 0)
        {
            // found one
            area->prs = pressed;
            area->mvo = moved_out;
            area->rls = released;
            area->lst = rect_list;
            area->nor = num_of_rect;
            // return its id
            return i;
        }
    }

    // no slots left
    return INAREA_ERROR_NO_IDS_AVAILABLE;
}

void TSD_InareaRemove (inarea_id_t id)
{
    _inarea * area = _areas + id;
    // free slot
    area->nor = 0;
    // remove reference to the list of rectangles
    area->lst = 0;
    // remove references to callbacks
    area->prs = area->mvo = area->rls = 0;
}

void TSD_InareaRemoveAll (void)
{
    int id;

    // iterate all slots
    for (id = 0; id < INAREA_CONFIG_MAXIMUM_AREAS; ++id)
    {
        // for each occupied slot
        if (_areas [id].nor != 0)
        {
            // remove its area
            TSD_InareaRemove(id);
        }
    }
}

static int _is_point_inside (unsigned int x, unsigned int y, _inarea* area)
{
    int x1, y1, x2, y2;
    inarea_rect_t * rect = area->lst;
    inarea_rect_t * p = rect + area->nor;

    // iterate all rectangles that compose this area
    for (rect = area->lst, p = rect + area->nor; rect < p; ++rect)
    {
        // is the point inside this rectangle?
        x2 = rect->width + (x1 = rect->x);
        y2 = rect->height + (y1 = rect->y);
        if ((x1 <= x) && (x2 >= x) && (y1 <= y) && (y2 >= y))
        {
            // yes it is
            return 1;
        }
    }

    // the point is not inside the area
    return 0;
}

static inline void _call (inarea_callback_t f, inarea_rect_t* lst)
{
    if (f)
    {
        f(lst);
    }
}

//------------------------------------------------------------------------------
/// Callback called when the pen is pressed on the touchscreen
/// /param horizontal position (in pixel if ts calibrated)
/// /param vertical position (in pixel if ts calibrated)
//------------------------------------------------------------------------------
void TSD_PenPressed (unsigned int x, unsigned int y)
{
    int i;
    _inarea * area;

    // iterate trough all slots
    for (i = 0; i < INAREA_CONFIG_MAXIMUM_AREAS; ++i)
    {
        area = _areas + i;
        // is there an area in this slot?
        if (area->nor != 0)
        {
            // yes
            // is the touched point inside this area?
            if (_is_point_inside(x, y, area))
            {
                // yes
                // save area's id
                _curr_area = i;
                // call prs
                _call(area->prs, area->lst);
                return;
            }
        }
    }
}

//------------------------------------------------------------------------------
/// Callback called when the pen is moved on the touchscreen
/// /param horizontal position (in pixel if ts calibrated)
/// /param vertical position (in pixel if ts calibrated)
//------------------------------------------------------------------------------
void TSD_PenMoved (unsigned int x, unsigned int y)
{
    _inarea * area;

    if (_curr_area == INAREA_CONFIG_MAXIMUM_AREAS)
    {
        // no area was touched
        return;
    }

    // is the "touching" still in same area?
    area = _areas + _curr_area;
    if (_is_point_inside(x, y, area))
    {
        // yes
        return;
    }
    // no
    // call mvo
    if (!_has_moved_out)
    {
        _call(area->mvo, area->lst);
    }
    // set mvo flag
    _has_moved_out = ~0;
}

//------------------------------------------------------------------------------
/// Callback called when the touchscreen is released on the touchscreen
/// /param horizontal position (in pixel if ts calibrated)
/// /param vertical position (in pixel if ts calibrated)
//------------------------------------------------------------------------------
void TSD_PenReleased (unsigned int x, unsigned int y)
{
    _inarea * area;

    if (_curr_area == INAREA_CONFIG_MAXIMUM_AREAS)
    {
        // no area was touched
        return;
    }

    // call rls
    area = _areas + _curr_area;
    _call(area->rls, area->lst);
    // no area is being touched
    _curr_area = INAREA_CONFIG_MAXIMUM_AREAS;
    // reset mvo flag
    _has_moved_out = 0;
}
